import datetime
import os
import time

import pandas as pd
import requests
from PyQt5.QtCore import QEvent, pyqtSignal, QThread, QMutex, QMutexLocker
from PyQt5.QtWidgets import QWidget, QMessageBox, QFileDialog

from distributor.distributor_ui import distributer_ui


class SessionExpiredError(Exception):
    """自定义异常，用于表示Session已失效。"""
    pass


from collections import defaultdict


class WorkerThread(QThread):
    """
    工人线程：只负责处理一小块数据，执行比对和分类任务。
    """
    # 修正信号定义，增加 item_processed 用于实时进度更新
    item_processed = pyqtSignal(str)
    work_finished = pyqtSignal(int, list, list, list)
    error = pyqtSignal(int, str)

    # 修正__init__参数，移除不再需要的 bill_id 和 bill_name
    def __init__(self, worker_id, data_chunk, session, system_orders_map, main_window_ref, bill_id):
        super().__init__()
        self.worker_id = worker_id
        self.data_chunk = data_chunk
        self.session = session
        self.system_orders_map = system_orders_map
        self.main_window = main_window_ref
        self.is_running = True
        self.bill_id = bill_id

    def run(self):
        try:
            successful_matches, price_mismatches, order_not_found = [], [], []

            for excel_item in self.data_chunk:
                if not self.is_running:
                    break

                order_id = excel_item['order']
                try:
                    excel_price = float(excel_item['price'])
                except (ValueError, TypeError):
                    continue

                matching_system_orders = self.system_orders_map.get(order_id)

                # --- 在这里可以调用实际的业务逻辑，例如价格更新 ---

                if not matching_system_orders:
                    order_not_found.append(excel_item)
                else:
                    perfect_match_found = False
                    for system_item in matching_system_orders:
                        if float(system_item.get('receiveAmount', 0)) == excel_price:
                            combined_item = {**excel_item, 'system_info': system_item}
                            easy_bill_id = self.bill_id[2:]
                            order_code = system_item['businessOrderCode']
                            self.add_order_to_bill(easy_bill_id, order_code)
                            successful_matches.append(combined_item)
                            perfect_match_found = True
                            break
                    if not perfect_match_found:
                        mismatch_item = {**excel_item, 'possible_matches': matching_system_orders}
                        price_mismatches.append(mismatch_item)

                # 【修正】发射单项处理完成信号，用于更新进度条
                self.item_processed.emit(f"订单 {order_id} 比对完毕。")

            # 所有任务完成后，发射 work_finished 信号
            self.work_finished.emit(self.worker_id, successful_matches, price_mismatches, order_not_found)
        except Exception as e:
            self.error.emit(self.worker_id, f"处理中出错: {e}")

    def stop(self):
        # 【修正】停止逻辑应设为 False
        self.is_running = False

    def add_order_to_bill(self, bill_id, order_code):
        url = "http://szhangjia.fangcang.com/fangcang-finance-web/agentBillController/addOrderToBill.shtml"
        payload = {
            "billId": bill_id,
            "financeOrderType": 1,
            "businessOrderCodeList": [order_code]
        }
        cookies = {
            "SESSION": self.session,
        }
        response = requests.post(url, cookies=cookies, json=payload)
        response.raise_for_status()
        response_json = response.json()
        result =  response_json['result']
        if result == 1:
            return True
        else:
            return False


# --- 2. 总指挥/管理者线程 ---
class BusinessThread(QThread):
    """
    总指挥线程：负责所有后台任务，并在最后将结果直接保存到Excel。
    """
    overall_progress = pyqtSignal(int, int, str)
    finished = pyqtSignal(str)
    error = pyqtSignal(str)

    def __init__(self, params, main_window_ref):
        super().__init__()
        self.params = params
        self.main_window = main_window_ref
        self.workers = []
        self.mutex = QMutex()
        self.processed_items_count = 0
        self.finished_worker_count = 0
        self.active_worker_count = 0
        self.total_tasks = 0
        self.all_successful_matches = []
        self.all_price_mismatches = []
        self.all_order_not_found = []

    def run(self):
        try:
            self.overall_progress.emit(0, 100, "阶段1：获取系统订单...")
            system_orders_list = self.get_fangcang_order_bills(self.params['bill_name'])

            system_orders_map = defaultdict(list)
            for item in system_orders_list:
                # 假设使用 distributorOrderCode 作为唯一键
                if key := item.get('distributorOrderCode'):
                    system_orders_map[key].append(item)

            self.overall_progress.emit(0, 100, "阶段2：读取并匹配Excel数据...")
            header_row = 0 if self.params['has_header'] else None
            df = pd.read_excel(self.params['file_path'], header=header_row, dtype=str)
            order_col_index = self.convert_col_to_index(self.params['order_col'])
            price_col_index = self.convert_col_to_index(self.params['price_col'])
            excel_data_df = pd.DataFrame(
                {'order': df.iloc[:, order_col_index], 'price': df.iloc[:, price_col_index]}).dropna()
            excel_data_list = excel_data_df.to_dict('records')

            # 直接将整个Excel数据作为任务列表，比对逻辑交给工人
            self.total_tasks = len(excel_data_list)
            if self.total_tasks == 0:
                raise ValueError("Excel文件中未找到有效数据。")

            self.overall_progress.emit(0, self.total_tasks, "阶段3：创建并派发对账任务...")
            thread_count = self.params['thread_num']
            chunks = [excel_data_list[i::thread_count] for i in range(thread_count)]

            self.active_worker_count = sum(1 for chunk in chunks if chunk)
            if self.active_worker_count == 0:
                self.finished.emit("没有任务需要执行。")
                return

            for i in range(thread_count):
                if not chunks[i]:
                    self.on_worker_finished(i, [], [], [])
                    continue

                # 【修正】创建WorkerThread时的参数和信号连接
                worker = WorkerThread(
                    worker_id=i,
                    data_chunk=chunks[i],
                    session=self.params['session'],
                    system_orders_map=system_orders_map,
                    main_window_ref=self.main_window,
                    bill_id=self.params['bill_id']
                )
                worker.item_processed.connect(self.on_item_processed)
                worker.work_finished.connect(self.on_worker_finished)
                worker.error.connect(self.on_worker_error)
                self.workers.append(worker)
                worker.start()
        except Exception as e:
            self.error.emit(str(e))

    def on_item_processed(self, message):
        with QMutexLocker(self.mutex):
            self.processed_items_count += 1
            self.overall_progress.emit(self.processed_items_count, self.total_tasks, message)

    def on_worker_finished(self, worker_id, successful, mismatched, not_found):
        with QMutexLocker(self.mutex):
            self.all_successful_matches.extend(successful)
            self.all_price_mismatches.extend(mismatched)
            self.all_order_not_found.extend(not_found)
            self.finished_worker_count += 1
            if self.finished_worker_count == self.active_worker_count:
                try:
                    filename = self.save_results_to_excel()
                    self.finished.emit(f"任务完成！结果已保存至:\n{filename}")
                except Exception as e:
                    self.error.emit(f"保存Excel文件时出错: {e}")

    def on_worker_error(self, worker_id, message):
        self.error.emit(f"线程 {worker_id} 发生错误: {message}")
        self.stop_all_workers()

    def save_results_to_excel(self):
        """将汇总的数据保存到Excel文件中"""
        # 1. 生成文件名
        distributor_name = self.params['bill_name']
        timestamp = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
        filename = f"{distributor_name}_{timestamp}_对账结果.xlsx"

        # 2. 准备写入器
        with pd.ExcelWriter(filename, engine='openpyxl') as writer:
            # 3. 处理并写入“成功匹配”的工作表
            if self.all_successful_matches:
                # 格式化数据以便写入
                success_data_flat = [{
                    'Excel订单号': item['order'],
                    'Excel金额': item['price'],
                    '系统入住人': item['system_info'].get('checkMan'),
                    '系统应收': item['system_info'].get('receiveAmount'),
                    '系统渠道': item['system_info'].get('agentName')
                } for item in self.all_successful_matches]
                success_df = pd.DataFrame(success_data_flat)
                success_df.to_excel(writer, sheet_name='成功匹配', index=False)

            # 4. 处理并写入“金额不一致”的工作表
            if self.all_price_mismatches:
                mismatch_data_flat = []
                for item in self.all_price_mismatches:
                    for sys_match in item['possible_matches']:
                        mismatch_data_flat.append({
                            'Excel订单号': item['order'],
                            'Excel金额': item['price'],
                            '系统匹配订单号': sys_match.get('orderCode'),
                            '系统匹配金额': sys_match.get('receiveAmount'),
                            '系统入住人': sys_match.get('checkMan')
                        })
                mismatch_df = pd.DataFrame(mismatch_data_flat)
                mismatch_df.to_excel(writer, sheet_name='金额不一致', index=False)

            # 5. 处理并写入“订单不存在”的工作表
            if self.all_order_not_found:
                not_found_df = pd.DataFrame(self.all_order_not_found)
                not_found_df.to_excel(writer, sheet_name='系统订单不存在', index=False)

        return filename

    def stop_all_workers(self):
        for worker in self.workers: worker.stop()

    def convert_col_to_index(self, col_str):
        # 【修正】移除重复的定义
        index = 0
        power = 1
        for char in reversed(col_str.upper()):
            if 'A' <= char <= 'Z':
                index += (ord(char) - ord('A') + 1) * power; power *= 26
            else:
                raise ValueError(f"列名 '{col_str}' 包含无效字符。")
        return index - 1

    # --- 【新增/恢复】缺失的网络请求方法 ---
    def get_fangcang_order_bills(self, bill_name):
        distributer_code = self.get_distributor_code(bill_name)
        today_date = datetime.date.today()
        first_day_of_year_date = datetime.date(today_date.year, 1, 1)
        url = "http://szhangjia.fangcang.com/fangcang-finance-web/orderController/listOrderForPage.shtml"
        cookies = {"SESSION": self.params['session']}
        payload = {
            "businessType": "1", "financeOrderType": "1",
            "checkInBeginDate": first_day_of_year_date.strftime("%Y-%m-%d"),
            "checkInEndDate": today_date.strftime("%Y-%m-%d"),
            "billType": "1", "currentPage": "1", "pageSize": "10000",
            "agentCode": distributer_code
        }
        response = requests.post(url, cookies=cookies, json=payload)  # 使用 data 而非 json
        response.raise_for_status()
        return response.json().get('obj', {}).get('itemList', [])

    def get_distributor_code(self, name):
        url = "http://szhangjia.fangcang.com/fangcang-finance-web/orgController/listAgentName.shtml"
        payload = {"keyword": name}
        cookies = {"SESSION": self.params['session']}
        response = requests.post(url, json=payload, cookies=cookies)  # 使用 data 而非 json
        response.raise_for_status()
        data = response.json()
        if data and data.get('obj') and len(data['obj']) > 0:
            return data['obj'][0].get("orgCode")
        raise ValueError(f"未能找到分销商 '{name}' 对应的编码。")


class distributor_main(QWidget):
    def __init__(self):
        super().__init__()
        self.ui = distributer_ui()
        self.ui.setupUi(self)
        self.initialize_logic()

    def initialize_logic(self):
        """
        初始化所有UI控件的逻辑和连接。
        """
        # --- 需求1：为账单地址输入框设置文件选择功能 ---
        self.ui.bill_adress_lineEdit.setReadOnly(True)
        self.ui.bill_adress_lineEdit.installEventFilter(self)

        # --- 需求2：绑定滑轨和线程数标签 ---
        self.thread_values = [1, 2, 3, 5, 8, 12, 16, 20, 32, 50, 65]
        self.ui.thread_slider.setMinimum(0)
        self.ui.thread_slider.setMaximum(len(self.thread_values) - 1)
        self.ui.thread_slider.valueChanged.connect(self.update_thread_label)

        # --- 【核心修改点】根据CPU线程数设置初始值 ---
        # 步骤2：获取CPU线程数
        cpu_thread_count = os.cpu_count()

        # 如果无法获取CPU数量，则设置一个安全的默认值，比如8
        if not cpu_thread_count:
            cpu_thread_count = 8

        # 步骤3：在预设值中找到最接近且不大于CPU线程数的那个值
        # 默认为列表中的第一个值，以防CPU核心数过低
        default_thread_count = self.thread_values[0]
        for value in self.thread_values:
            if value <= cpu_thread_count:
                default_thread_count = value
            else:
                # 因为列表是递增的，一旦超过就可以停止查找
                break

        # 步骤4：根据计算出的默认值，设置滑轨的初始位置
        if default_thread_count in self.thread_values:
            default_index = self.thread_values.index(default_thread_count)
            self.ui.thread_slider.setValue(default_index)

        # 步骤5：手动调用一次更新函数，以保证程序启动时标签显示正确的初始值
        self.update_thread_label(self.ui.thread_slider.value())
        # --- 修改结束 ---

        # --- 需求3：设置复选框默认为勾选状态 ---
        self.ui.have_head_checkBox.setChecked(True)

        self.ui.start_pushButton.clicked.connect(self.start_task)

    def eventFilter(self, obj, event):
        """
        事件过滤器，用于捕获特定控件上的事件。
        """
        # 检查事件是否发生在我们的目标控件上 (bill_adress_lineEdit)
        if obj == self.ui.bill_adress_lineEdit:
            # 检查事件是否是鼠标点击事件
            if event.type() == QEvent.MouseButtonPress:
                # 如果是，则调用文件对话框函数
                self.open_bill_file_dialog()
                # 返回True表示我们已经处理了此事件，不需要再向下传递
                return True

        # 对于所有其他事件，返回默认的事件过滤器行为
        return super().eventFilter(obj, event)

    def open_bill_file_dialog(self):
        """
        打开文件对话框让用户选择表格文件。
        """
        # 打开文件对话框，getOpenFileName 返回一个元组 (文件路径, 文件类型过滤器)
        file_path, _ = QFileDialog.getOpenFileName(
            self,
            "请选择账单文件",  # 对话框标题
            "",  # 默认打开的目录
            "Excel 文件 (*.xlsx *.xls);;所有文件 (*)"  # 文件类型过滤器
        )

        # 如果用户选择了文件 (即 file_path 不为空)
        if file_path:
            # 检查文件后缀是否为表格文件
            if file_path.endswith('.xlsx') or file_path.endswith('.xls'):
                # 如果是，将文件路径显示在输入框中
                self.ui.bill_adress_lineEdit.setText(file_path)
            else:
                # 如果不是，弹窗提醒用户
                QMessageBox.warning(self, "文件类型错误", "请选择一个有效的Excel表格文件 (.xlsx 或 .xls)。")
                # 清空输入框内容
                self.ui.bill_adress_lineEdit.clear()

    def update_thread_label(self, value):
        """
        当滑轨值改变时，更新线程数标签的文本。
        """
        # value 是滑轨的当前值，也就是列表的索引
        thread_count = self.thread_values[value]
        # 将标签的文本设置为对应的线程数
        self.ui.thread_label.setText(str(thread_count))

    def start_task(self):
        """
        点击“开始执行”按钮后触发的函数，包含输入校验。
        """
        # --- 步骤1：定义需要检查的字段及其对应的中文名称 ---
        fields_to_check = [
            (self.ui.fangcang_bill_id_lineEdit, "账单编号"),
            (self.ui.bill_adress_lineEdit, "账单文件"),
            (self.ui.fangcang_session_lineEdit, "房仓Session"),
            (self.ui.order_index_lineEdit, "订单号列"),
            (self.ui.price_index_lineEdit, "金额列")
        ]

        # --- 步骤2：遍历并检查每个字段是否为空 ---
        missing_fields = []
        for widget, name in fields_to_check:
            # .strip() 用于去除首尾空格，防止用户只输入了空格
            if not widget.text().strip():
                missing_fields.append(name)

        # --- 步骤3：如果存在空字段，则弹窗提醒并中断任务 ---
        if missing_fields:
            # 构造清晰的错误信息
            error_message = "执行任务前，请填写以下必要信息：\n\n" + "\n".join(f" - {field}" for field in missing_fields)

            # 弹出警告框
            QMessageBox.warning(self, "信息不完整", error_message)

            # 中断函数执行
            return

        # --- 步骤4：检查账单号是否有效 ---
        fangcang_bill_id = self.ui.fangcang_bill_id_lineEdit.text()
        try:
            session = self.ui.fangcang_session_lineEdit.text()
            bill_name = self.get_bill_info(fangcang_bill_id, session)

            if bill_name is None:
                error_message = f"错误：没获取账单编号：{fangcang_bill_id} 的分销账单"
                self.ui.status_label.setText(error_message)
                QMessageBox.critical(self, "错误", error_message)

            params = {
                'bill_id': fangcang_bill_id,
                'bill_name': bill_name,
                'session': self.ui.fangcang_session_lineEdit.text().strip(),
                'file_path': self.ui.bill_adress_lineEdit.text().strip(),
                'order_col': self.ui.order_index_lineEdit.text().strip(),
                'price_col': self.ui.price_index_lineEdit.text().strip(),
                'has_header': self.ui.have_head_checkBox.isChecked(),
                'thread_num': int(self.ui.thread_label.text())
            }

            # 启动唯一的“总指挥”线程
            self.business_thread = BusinessThread(params, self)
            self.business_thread.overall_progress.connect(self.update_progress_status)
            self.business_thread.finished.connect(self.on_task_finished)
            self.business_thread.error.connect(self.on_task_error)
            self.business_thread.start()

            # --- 步骤：如果所有字段都通过校验，则继续执行主任务 ---
            self.ui.status_label.setText("校验通过，开始执行任务...")
            print("所有字段验证通过，可以开始执行核心任务了。")
        except SessionExpiredError as e:
            # 捕获到 Session 失效的“警报”
            self.ui.status_label.setText("错误：Session已失效")
            QMessageBox.critical(self, "会话已失效", str(e))
            # 此处已经自动“结束了本次业务执行”，因为代码已经跳出了try块
            return

        except ConnectionError as e:
            # 捕获到网络连接错误
            self.ui.status_label.setText("错误：网络请求失败")
            QMessageBox.critical(self, "网络错误", str(e))
            return

        except Exception as e:
            # 捕获其他未知异常，增加程序健壮性
            self.ui.status_label.setText(f"发生未知错误: {e}")
            QMessageBox.critical(self, "未知错误", f"程序运行中遇到一个意外错误：\n{e}")
            return

    def get_bill_info(self, fangcang_bill_id, session):
        today_date = datetime.date.today()
        first_day_of_year_date = datetime.date(today_date.year, 1, 1)

        today_str = today_date.strftime("%Y-%m-%d")
        first_day_of_year_str = first_day_of_year_date.strftime("%Y-%m-%d")
        url = "http://szhangjia.fangcang.com/fangcang-finance-web/agentBillController/listBillForPage.shtml"
        payload = {
            "billCode": fangcang_bill_id,
            "createBeginDate": first_day_of_year_str,
            "createEndDate": today_str,
            "currentPage": 1,
            "pageSize": 20
        }
        cookies = {
            "SESSION": session,
        }
        try:
            response = requests.post(url, json=payload, cookies=cookies)
            response.raise_for_status()  # 如果状态码不是2xx，也会抛出异常
        except requests.exceptions.RequestException as e:
            # 抛出我们自己的异常，或者直接处理
            raise ConnectionError(f"网络请求失败: {e}")
        if response.status_code == 200:
            if '<title>后台管理系统</title>' in response.text:
                raise SessionExpiredError("房仓Session已失效或错误，请更新后重试！")

            response_json = response.json()
            item_list = response_json['obj']['itemList']
            if len(item_list) == 0:
                return None
            for item in item_list:
                orgName = item['orgName']
                return orgName

    def on_task_finished(self, message):
        """当总指挥线程报告任务完成时调用"""
        self.ui.status_label.setText(message)
        QMessageBox.information(self, "任务完成", message)
        self.ui.start_pushButton.setEnabled(True)

    def update_progress_status(self, current, total, message):
        """更新状态标签以显示对账进度"""
        self.ui.status_label.setText(f"({current}/{total}) {message}")

    def on_reconciliation_finished(self, message):
        """当对账任务全部完成时调用"""
        self.ui.status_label.setText(message)
        QMessageBox.information(self, "任务完成", message)
        self.ui.start_pushButton.setEnabled(True)

    def on_task_error(self, message):
        """当任何一个线程或步骤发生错误时调用"""
        self.ui.status_label.setText(f"错误：{message}")
        QMessageBox.critical(self, "任务中断", message)
        self.ui.start_pushButton.setEnabled(True)
